
/* Copyright 2024 Coremail
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import type {
  EmailAddress, FolderData, IBuffer, MailBasic,
  Order, Properties,
  Query,
  IMailStorage,
  Filter,
  Mime,
  IBufferCreator,
  BufferHint,
  MailStructure,
} from '@coremail/mail_base';

import {
  StoreFeature,
  MailAttribute,
  CMError,
  ErrorCode
} from '@coremail/mail_base';

import util from "@ohos.util"
import fs from '@ohos.file.fs';

import { EntityTransformer } from '../basedata/EntityTransformer';
import { FolderEntity, FolderId } from '../entities/FolderEntity';
import type { Context } from '@ohos.abilityAccessCtrl';
import { getLogger } from '@coremail/mail_base';
import { MailDbDataOrm } from '../basedata/MailDbDataOrm';
import { FileBuffer } from '../basedata/FileBuffer';
import { getMimeParts, MimeMail, parseMime2 } from '@coremail/mail_base/src/main/ets/utils/mime';
import { MemBuffer } from '@coremail/mail_base/src/main/ets/utils/file_stream';

import { EmlFileManager } from '../basedata/EmlFileManager';
import { FileManager } from '../utils/FileManger';
import { MailEntity } from '../entities/MailEntity';
import type { Attachment } from '../entities/MailEntity';
import { FileUtil } from '../utils/FileUtil';
import { ParseUtil } from '../utils/ParseUtil';
import { getTaskLogger } from '../utils/LogUtil';
import { IMPORTANT_FOLDER_NAME } from './sync_store';

const TAG: string = "StorageStore";
const logger = getLogger(TAG);
const taskLogger = getTaskLogger("InitFolder");
const countLogger = getTaskLogger("__unreadCount");
const draftLogger = getTaskLogger("__draftLogger");
const folderLogger = getTaskLogger("__folder");

export function createBufferCreator(basePath: string, account: string): IBufferCreator {
  const base64 = new util.Base64Helper();
  const b = new Uint8Array(account.split('').map(v => v.charCodeAt(0)));
  const path = `${basePath}/${base64.encodeToStringSync(b)}`
  if (!fs.accessSync(path)) {
    fs.mkdirSync(path);
  }
  let index = 0;
  const bufferCreator: IBufferCreator = {
    createBuffer(hint: BufferHint) {
      if (hint.useFile || hint.size >= 2 * 1024 * 1024) { // 数据较大使用文件
        index += 1;
        const filePath = `${path}/${index}`;
        return new FileBuffer(filePath);
      } else {
        return new MemBuffer();
      }
    }
  }
  return bufferCreator;
}

export class MailStorage implements IMailStorage {

  private p_setFolderList:Promise<void>|null = null;
  /**
   *
   * @param folderFullName
   * @param newMailList
   * @returns
   */
  async addMailBasicList(folderFullName: string, newMailList: MailBasic[]): Promise<void> {
    const folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    const folderId = folder.id;
    for (const mailBasic of newMailList) {
      logger.trace(`[addMailBasicList] [${folderId},id: ${mailBasic.id}]`, mailBasic);
      let mailId = mailBasic.id;
      getMimeParts(mailBasic.structure, (struct: MailStructure) => {
        if (struct.encoding) {
          struct.encoding = '8bit';
        }
        return false;
      })
      this.storageDB.mailDaoImpl.insertOrReplaceMailWithBasic(mailId, mailBasic, folderId);
    }
  }

  /**
   * 查询
   * @param query
   * @returns 匹配的邮件mid数组.
   */
  async searchMail(query: Query): Promise<string[]> {
    let entities:MailEntity[] = [];
    let folderId: number | null = null;
    try {
      const folder = await this.storageDB.folderDaoImpl.getFolderByFullName(query.folder);
      folderId = folder.id;
    }
    catch (e) {
      logger.error(`[search mail,no folder found with fullname :${query.folder}]`)
      return [];
    }
    try {
      entities = await this.storageDB.mailDaoImpl.searchMail(query, folderId);
    } catch (e) {
      logger.error(`[search mail,search mail failed with query: ${query}`);
      return [];
    }
    let mids:string[] = entities.map((entity:MailEntity) => {
      return entity.mid;
    })
    return mids;
  }


  /**
   * 数据库是否存在该邮件
   * 不需要指定文件夹.
   * * 说明是pop3协议,不需要进行文件夹的验证.
   * @param folderFullName
   * @param mailId
   * @returns
   */
  async hasMail(folderFullName: string, mailId: string): Promise<boolean> {
    await this.init()
    let folderId:number|null = null;
    if (folderFullName !== "*") {
      let folderEntity: FolderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
      if (!folderEntity) {
        return Promise.reject("no such folder");
      }
      folderId = folderEntity.id;
    }
    return this.storageDB.mailDaoImpl.hasMailByRawSql(mailId, folderId);
  }

  async checkMailExist(mailIdList: string[]): Promise<{
    basic: boolean,
    full: boolean
  }[]> {
    throw new CMError("not implementioned", ErrorCode.NOT_IMPLEMENTED);
    // let folderEntity: FolderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    // if(!folderEntity) {
    //   return Promise.reject("no such folder");
    // }
    // return this.storageDB.mailDaoImpl.checkMailExist(mailIdList,folderEntity.id);
  }


  async hasMails(folderFullName: string, mailIds: string[]): Promise<string[]> {
    await this.init()
    return this.storageDB.mailDaoImpl.hasMails(mailIds);
  }


  context: Context
  storageDB: MailDbDataOrm;
  _remoteFolderDataList: FolderData[]
  features = [StoreFeature.Folder | StoreFeature.MailPart | StoreFeature.MailAttribute];
  fileRootDir: string;
  properties: Properties;
  account: string;
  _fileBufferCreator: IBufferCreator;
  private isInit = false;

  async getFolderList(): Promise<FolderData[]> {
    await this.init();
    let folderDatas = await this.getAllFolderList();
    logger.debug(`[getFolderList] [__folder] [folders are: ${JSON.stringify(folderDatas)}]`);
    return folderDatas;
  }

  // setProperties(context:Context,account:string) {
  //   this.context = context;
  //   this.account = account;
  // }

  constructor(context: Context, account: string) {
    this.context = context;
    this.account = account;
    this.storageDB = new MailDbDataOrm(context, account);
    this.fileRootDir = context.filesDir;
    this._fileBufferCreator = createBufferCreator(context.filesDir, account);
  }

  getBufferCreator(): IBufferCreator {
    return this._fileBufferCreator;
  }

  async getImpFolderInfo(): Promise<FolderData> {
    let unreadCount = await this.storageDB.folderDaoImpl.getImpUnreadCount();
    countLogger.debug(`[MailStorage] [getImpFolderInfo] [unreadCount is ${unreadCount}]`)
      let folder:FolderData = {
        name:IMPORTANT_FOLDER_NAME,
        fullName:IMPORTANT_FOLDER_NAME,
        unreadCount:unreadCount,
        mailCount:0,
        parent:""
      }
      return folder;
  }

  /**
   * 更新文件夹的未读数.
   * 方案1:自己重新查询更新一遍.
   * 方案2:store服务端获取未读数,然后插入到本地.
   * @returns
   */
  async updateFolderUnreadCountByDelta(fullName:string,delta:number):Promise<void> {
    countLogger.info(`[updateFolderUnreadCount] ${fullName}`)
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(fullName);
    if (!folder) {
      countLogger.trace(`[no such folder:${fullName}]`);
      return Promise.reject("no such folder");
    }
    let unreadCount = await this.storageDB.folderDaoImpl.getFolderUnreadCount(folder.id);
    // countLogger.trace(`[MailStorage][getFolderInfo ${fullName} ${unreadCount}]`)
    // let mailCount = await this.storageDB.folderDaoImpl.getFolderMailCount(folder.id);
    folder.unReadCount = unreadCount + delta;
    // folder.mailCount = mailCount;
    await this.storageDB.folderDaoImpl.updateFolder(folder);
  }

  async updateFolderUnreadCountByCal(folder:FolderEntity):Promise<void> {
    // countLogger.info(`[updateFolderUnreadCount] ${fullName}`)
    // let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(fullName);
    // if (!folder) {
    //   countLogger.trace(`[no such folder:${fullName}]`);
    //   return Promise.reject("no such folder");
    // }
    let unreadCount = await this.storageDB.folderDaoImpl.getFolderUnreadCount(folder.id);
    countLogger.trace(`[MailStorage][updateFolderUnreadCountByCal ${folder.name} ${unreadCount}]`)
    let mailCount = await this.storageDB.folderDaoImpl.getFolderMailCount(folder.id);
    folder.unReadCount = unreadCount;
    folder.mailCount = mailCount;
    await this.storageDB.folderDaoImpl.updateFolder(folder);
  }

  /**
   * getFolderInfo
   * 未读数是取folder表中保存的 还是 mail表中查出来的?
   * 未读数的保存问题.
   * 1,读取folder中的未读数.
   * 2,从mail表中查
   * TODO:目前没有接口让文件夹刷新未读数,只能每次获取文件夹信息的时候都查一遍.
   * 在数据库信息同步完整之前不应该用storage获取getFolderInfo,未读数不正确.
   * @param fullName
   * @returns
   */
  async getFolderInfo(fullName: string): Promise<FolderData> {
    if(fullName == IMPORTANT_FOLDER_NAME) {
      let fd = await  this.getImpFolderInfo();
      return fd;
    }
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(fullName);
    if (!folder) {
      taskLogger.trace(`[no such folder:${fullName}]`);
      return Promise.reject("no such folder");
    }
    //
    await this.updateFolderUnreadCountByCal(folder)
    let folderData = EntityTransformer.getFolderDataWithFullName(folder, fullName);
    return folderData;
  }

  async setFolderInfo(folderData: FolderData): Promise<void> {
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderData.fullName);
    if (!folder) {
      return Promise.reject("no such folder");
    }
    folder.unReadCount = folderData.unreadCount;
    folder.mailCount = folderData.mailCount;
    this.storageDB.folderDaoImpl.updateFolder(folder);
  }


  /**
   * @param srcFolderFullName
   * @param srcMailId
   * @param dstFolderFullName
   * @param dstMaillId
   * @returns
   */
  async copyMail(srcFolderFullName: string, srcMailId: string, dstFolderFullName: string, dstMaillId: string): Promise<void> {
    let dstFolder = await this.storageDB.folderDaoImpl.getFolderByFullName(dstFolderFullName);
    if (!dstFolder) {
      return Promise.reject("no dst folder");
    }
    let srcFolder = await this.storageDB.folderDaoImpl.getFolderByFullName(srcFolderFullName);
    if (!srcFolder) {
      return Promise.reject("no src folder");
    }
    let srcMail = await this.storageDB.mailDaoImpl.getMail(srcMailId, srcFolder.id);
    if (!srcMail) {
      return Promise.reject("no such mail");
    }
    let hasDstMail = await this.storageDB.mailDaoImpl.hasMailByRawSql(dstMaillId, dstFolder.id);
    if (hasDstMail) {
      //数据异常,目标文件夹有这个mid的数据了,直接返回.并结束拷贝过程,会将次邮件删除.
      //eg:移动一封历史数据邮件,该邮件服务器上没有,返回的mid其实是其他文件的mid.
      // return Promise.reject("dst mail exsited");
      return;
    }
    let dstMail = new MailEntity();
    dstMail.id = null;
    dstMail.mid = dstMaillId;
    dstMail.folderId = dstFolder.id;
    dstMail.receivedDate = srcMail.receivedDate;
    //需要将源文考本一份过去.
    if (srcMail.emlPath) {
      let dstRelativeMailPath = EmlFileManager.getRelativeEmlPathForMail(this.account, dstMaillId, dstFolderFullName);
      let dstMailPath = `${this.fileRootDir}/${dstRelativeMailPath}`
      FileUtil.copyFileSync(srcMail.emlPath, dstMailPath);
      dstMail.emlPath = dstMailPath;

      //如果源邮件包含附件信息,需要将附件数据拷贝到新mid邮件对应的位置,这里采用重新parse一遍.
      if (srcMail.hasPartContent()) {
        await this.parseEml(dstMail, this.account, dstMail.mid, dstFolderFullName);
        dstMail.parsed = true;
      }
      //如果没有点过的,只有mailbasic信息,需要把mailbasic重新设置一次.
      //复制时,attr属性也要带上.
      dstMail.updateAttrBySet(srcMail.getAttr());
    }
    else {
      //没有eml源文.需要拷贝一次mailbasic
      dstMail.setMailBasic(srcMail.getMailBasic());
      //mailBasic包含了mid,需要更新mid
      dstMail.mid = dstMaillId;
    }
    //更新未读数
    await this.updateFolderUnreadCountByCal(srcFolder);
    await this.updateFolderUnreadCountByCal(dstFolder);

    logger.trace(`[__imap][copy mail][dst:${srcMail.subject},new id is: ${dstMaillId}]`)
    try {
      await this.storageDB.mailDaoImpl.insertMail(dstMail);
    }
    catch(e:unknown) {
      logger.error(`[insert failed]`)
    }
  }

  async moveMail(srcFolderFullName: string, mailId: string, dstFolderFullName: string, dstMailId: string): Promise<void> {
    logger.trace(`[__move][moveMail][${srcFolderFullName}${MailAttribute}==>${dstFolderFullName}${dstMailId}]`)
    let dstFolder = await this.storageDB.folderDaoImpl.getFolderByFullName(dstFolderFullName);
    if (!dstFolder) {
      return Promise.reject("no dst folder");
    }
    let hasDstMail = await this.storageDB.mailDaoImpl.hasMailByRawSql(dstMailId, dstFolder.id);
    if (hasDstMail) {
      //数据异常,目标文件夹有这个mid的数据了,很可能是因为这封邮件是存在于本地的邮件,这样的话,移动不需要修改mid,直接更新文件夹就可以了.
      dstMailId = mailId;
      logger.trace(`[${TAG}] [dstMailId has existed]`);
    }
    if (dstMailId === mailId) {
      //pop3 mailId不变,只更新folderId;
      let srcFolder = await this.storageDB.folderDaoImpl.getFolderByFullName(srcFolderFullName);
      if (!srcFolder) {
        return Promise.reject("no src folder");
      }
      let dstFolder = await this.storageDB.folderDaoImpl.getFolderByFullName(dstFolderFullName);
      if (!dstFolder) {
        return Promise.reject("no dst folder");
      }
      let srcMail = await this.storageDB.mailDaoImpl.getMail(mailId, srcFolder.id);
      if (!srcMail) {
        logger.warn(`[moveMail] [no such mail:${mailId} at ${srcFolderFullName}`);
        return;
      }
      srcMail.folderId = dstFolder.id;
      if (srcMail.emlPath && fs.accessSync(srcMail.emlPath)) {
        let dstRelativeMailPath = EmlFileManager.getRelativeEmlPathForMail(this.account, mailId, dstFolderFullName);
        let dstMailPath = `${this.fileRootDir}/${dstRelativeMailPath}`
        FileUtil.copyFileSync(srcMail.emlPath, dstMailPath);
        srcMail.emlPath = dstMailPath;

        //如果源邮件包含附件信息,需要将附件数据拷贝到新mid邮件对应的位置,这里采用重新parse一遍.
        if (srcMail.hasPartContent()) {
          try {
            await this.parseEml(srcMail, this.account, srcMail.mid, dstFolderFullName);
            srcMail.parsed = true;
          }
          catch (e:unknown) {
            logger.error("parse eml failed when move");
          }
        }
      }
      await this.storageDB.mailDaoImpl.updateMail(srcMail);
      await this.updateFolderUnreadCountByCal(srcFolder);
      await this.updateFolderUnreadCountByCal(dstFolder);
    }
    else {
      logger.debug(`[__imap] [copy mail ${srcFolderFullName}/${mailId} => ${dstFolderFullName}/${dstMailId}]`);
      await this.copyMail(srcFolderFullName, mailId, dstFolderFullName, dstMailId);
      logger.debug(`[__imap] [delete mail ${srcFolderFullName}/${mailId}]`);
      await this.deleteMail(srcFolderFullName, mailId);
      logger.debug(`[__imap] [deleted]`)
    }
  }

  /**
   * 删除指定邮件.
   * @param folderFullName
   * @param mailId
   * TODO: 删除附件图片.
   * @returns
   */
  async deleteMail(folderFullName: string, mailId: string): Promise<void> {
    logger.trace(`[__pop3][deleteMail][mid is: ${mailId}]`)
    let folderEntity: FolderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    if (!folderEntity) {
      return Promise.reject("no such folder");
    }
    await this.storageDB.mailDaoImpl.deleteMail(mailId, folderEntity.id);
    await this.updateFolderUnreadCountByCal(folderEntity);
    let dirPath = this.getDirPathForMail(mailId, this.account, folderFullName);
    this.removeDirR(dirPath);
    return
  }

  getDirPathForMail(mid: string, account: string, folderFullName: string): string {
    let relativeDirPath = EmlFileManager.getRelativeDirPathForMail(account, mid, folderFullName);
    let dirPath = `${this.fileRootDir}/${relativeDirPath}`;
    return dirPath;
  }

  getHtmlPathForMail(mid: string, account: string, folderFullName: string): string {
    let relativeDirPath = EmlFileManager.getRelativeHtmlPathForMail(account, mid, folderFullName);
    let dirPath = `${this.fileRootDir}/${relativeDirPath}`;
    return dirPath;
  }

  getTextPathForMail(mid: string, account: string, folderFullName: string): string {
    let relativeDirPath = EmlFileManager.getRelativeTextPathForMail(account, mid, folderFullName);
    let dirPath = `${this.fileRootDir}/${relativeDirPath}`;
    return dirPath;
  }

  /**
   * 删除单个文件
   * @param filePath
   */
  removeFile(filePath: string): void  {
    fs.unlinkSync(filePath)
  }

  /**
   * 删除dir
   */
  removeDir(dirPath: string):void {
    //可以删除有内容的文件夹吗?
    fs.rmdirSync(dirPath);
  }

  removeDirR(dirPath: string):void {
    if (this.isItemExist(dirPath) && this.isItemDir(dirPath)) {
      //可以删除有内容的文件夹吗?
      let subItems = this.fileListInDir(dirPath);
      for (const item of subItems) {
        if (this.isItemDir(item)) {
          this.removeDirR(item);
        }
        else {
          this.removeFile(item);
        }
      }
      fs.rmdirSync(dirPath);
    }
  }

  isItemExist(path: string): boolean {
    return fs.accessSync(path);
  }

  isItemDir(path: string): boolean{
    return fs.statSync(path).isDirectory();
  }

  /**
   * 返回一个文件夹的所有子item的itemPath.
   * @param dirPath
   * @returns
   */
  fileListInDir(dirPath: string): string[] {
    if (fs.accessSync(dirPath)) {
      let list = fs.listFileSync(dirPath);
      let itemPaths = list.map((value, index) => {
        let itemPath = `${dirPath}/${value}`
        return itemPath;
      });
      return itemPaths;
    }
    else {
      return [];
    }
  }


  /**
   * 获取所有的文件夹列表.
   * 是否对排序有要求?
   * @returns
   */
  async getAllFolderList(): Promise<FolderData[]> {
    let root = FolderEntity.FOLDER_ENTITY_ROOT;
    let arr: FolderData[] = [];
    let subEntities = await this.storageDB.folderDaoImpl.getFolderList(root.name)
    for (let sub of subEntities) {
      await this.addFolderAndSubsToArr(arr, sub, `${sub.name}`)
    }
    return arr;
  }

  /**
   * 获取邮件在当前排序顺序下的索引.
   * @param folderFullName
   * @param mailId
   * @param order
   * @returns
   */
  async getMailIndex(
    folderFullName: string,
    mailId: string,
    order: Order
  ): Promise<number> {
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    if (!folder) {
      return Promise.reject("no such folder");
    }
    let folderId = folder.id;
    let index = await this.storageDB.mailDaoImpl.getMailIndex(folderId, mailId, order);
    if (index == -1) {
      return Promise.reject("no such mail")
    }
    else {
      return index;
    }
  }

  /**
   * 将一个文件夹及其子文件夹entity转换并加到arr
   * @param arr
   * @param folder
   * @param fullName
   */
  async addFolderAndSubsToArr(arr: FolderData[], folder: FolderEntity, fullName: string): Promise<void> {
    await this.init();
    let fdata = EntityTransformer.getFolderDataWithFullName(folder, fullName);
    arr.push(fdata);
    let subEntities = await this.storageDB.folderDaoImpl.getFolderListByParentId(folder.id);
    for (let sub of subEntities) {
      await this.addFolderAndSubsToArr(arr, sub, `${fullName}/${sub.name}`)
    }
  }


  /**
   * 获取文件夹下的id的序号.
   * @param folderName
   * @param start 偏移量
   * @param size 大小
   * @param order 顺序.
   * @returns
   */
  async getFolderMailIdList(folderName: string, start: number, size: number, order: Order): Promise<string[]> {
    await this.init();
    let folderEntity: FolderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(folderName);
    if (!folderEntity) {
      return Promise.reject("no such folder");
    }
    let folderId = folderEntity.id;
    let mailEntities = await this.storageDB.mailDaoImpl.getFolderMailListByFolderId(folderId, start, size, order);
    let mids: string[] = mailEntities.map(
      (item) => {
        return item.mid
      });
    return mids;
  }

  /**
   * 添加邮件eml到数据库
   * @param folderFullName
   * @param mailId
   * 这个时候使用自己生成的唯一id. folderFullName+
   *
   *

   * @param mail
   * @returns
   */
  async addMail(folderFullName: string, mailId: string, mail: IBuffer): Promise<void> {
    await this.init();
    await this.addMailWithMimeAndMid(mail, mailId, folderFullName);
    //解析以得到正确的接受时间.
    await this.getMailBasic(folderFullName,mailId);
    logger.trace(`[__draft][save eml with mailId:${mailId}]`)
  }

  /**
   *存入邮件信息到数据库
   * mid 和 mime源文.
   * @param mail 邮件内容字符串.
   * MIME
   * @param folderName
   * MIME, eml源文
   *
   * TODO: 如果文件夹不存在,抛个异常.
   *
   * returns
   */
  async addMailWithMimeAndMid(mail: IBuffer, mid: string, folderFullName: string): Promise<void> {
    await this.init();
    logger.debug(`[__pop3][addMailWithMime][insert eml: ${mid}]`)
    let folderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    let folderId: number = folderEntity.id;
    await this.storageDB.mailDaoImpl.insertMailWithId(mail, mid, folderId, folderFullName);
  }

  async getMailPartContent(folderFullName: string, mailId: string, partId: string): Promise<IBuffer> {
    await this.init();
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    if (!folder) {
      return Promise.reject("no such folder");
    }
    let mail = await this.storageDB.mailDaoImpl.getMail(mailId, folder.id);
    if (!mail) {
      return Promise.reject("no such mail");
    }
    if (mail.emlPath) {
      await this.parseMailIfNeededAndUpdate(mail, folderFullName);
      let filePath = mail.getPartFilePath(partId);
      if (filePath) {
        return new FileBuffer(filePath);
      }
      else {
        return Promise.reject("no such part");
      }
    }
    else {
      return Promise.reject("no such mail");
    }
  }

  // async getMailPartContent2(folderFullName: string, mailId: string, partId: string): Promise<IBuffer> {
  //   await this.init();
  //   let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
  //   if(!folder) {
  //     return Promise.reject("no such folder");
  //   }
  //   let mail = await this.storageDB.mailDaoImpl.getMail(mailId,folder.id);
  //   if(!mail) {
  //     return Promise.reject("no such mail");
  //   }
  //   if(mail.emlPath) {
  //     let s = new FileBuffer(mail.emlPath);
  //     const mime = await parseMime2(s);
  //     if (!partId) {
  //       partId = "1";
  //     }
  //     const m = getMimeParts(mime, (m) => m.partId == partId)[0];
  //     return (m as Mime).body;
  //   }
  //   else {
  //     return Promise.reject("no such mail");
  //   }
  // }

  /**
   *
   * @param folderFullName
   * @param mailId
   * @returns
   */
  async getMailBasic(folderFullName: string, mailId: string): Promise<MailBasic> {
    await this.init();
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    if (!folder) {
      return Promise.reject("no such folder");
    }
    let mail = await this.storageDB.mailDaoImpl.getMail(mailId, folder.id);
    if (!mail) {
      return Promise.reject("no such mail");
    }
    logger.trace(`[__draft][getMailBasic][before parse] ${mail.mid}`);
    await this.parseMailIfNeededAndUpdate(mail, folderFullName);
    logger.trace(`[__draft][getMailBasic][after parse] ${mail.mid}`);
    let mailBasic = mail.getMailBasic();
    if (mail.parsed) {
      // getMimeParts(mailBasic.structure, (mime) => {mime.encoding = '8bit'; return false;})
    }
    logger.debug(`[getMailBasic] [] [mail basic of id:${mailId} ${JSON.stringify(mailBasic)}]`)
    return mailBasic;
  }

  async filterMail(filter: Filter): Promise<Map<string, string[]>> {
    const attrs = [];
    if (filter.isFlagged) {
      attrs.push(MailAttribute.Flagged);
    }
    const query = {
      folder: null,
      fields: {},
      attrs: attrs
    };
    let entities = await this.storageDB.mailDaoImpl.searchMail(query, null);
    let groups: Map<string, string[]> = new Map();
    for await (let entity of entities) {
      let folder: FolderEntity = await this.storageDB.folderDaoImpl.getFolderById(entity.folderId);
      if (!folder) {
        return Promise.reject("no such folder");
      }
      let fullName = await this.storageDB.folderDaoImpl.getFullName(folder);
      if (!groups.has(fullName)) {
        groups.set(fullName, [entity.mid]);
      }
      else {
        let arr = groups.get(fullName);
        arr.push(entity.mid);
      }
    }
    return groups;
  }


  /**
   * 解析mail的eml并保存数据.
   * 更新 的字段 size, envelope,mai
   * @param mail
   */
  async parseMailIfNeededAndUpdate(mail: MailEntity, folderFullName: string): Promise<void> {
      if (mail.needParse()) {
        draftLogger.trace(`[MailStorage] [parse eml] [before] ${mail.mid}`);
        await this.parseEml(mail, this.account, mail.mid, folderFullName);
        draftLogger.trace(`[MailStorage] [parse eml] [after] ${mail.mid}`);
        mail.parsed = true;
        return this.storageDB.mailDaoImpl.updateMail(mail);
      }
  }

  async parseEml(mail: MailEntity, account: string, mid: string, folderFullName: string): Promise<void> {
    let content = new FileBuffer(mail.emlPath);
    let stat = fs.statSync(mail.emlPath);
    let size = stat.size;
    if (size == 0) return;
    let mime: Mime | null = null;
    try {
      mime = await parseMime2(content, this._fileBufferCreator);
    }
    catch (e: unknown) {
      logger.error(`parse mime2 error`, e);
      draftLogger.error(`parse mime2 error`);
      return Promise.reject("parse mime2 error");
    }
    let mimeMail = new MimeMail(mime)
    let attachments = mimeMail.getAttachments();
    let inlineImages = mimeMail.getInlineImages();

    let _attachments: Attachment[] = [];
    try {
      _attachments = await Promise.all(attachments.map(async (item, index) => {
        const name = item.disposition?.params.filename || item.contentType.params.name;
        return {
          name: name,
          type: item.contentType.type,
          subType: item.contentType.subType,
          id: `${index}`,
          encoding: item.encoding,
          size: item.size,
          index: index,
          localPath: await this.downloadStreamIfNeededAndDecode(item as Mime, mid, index, false, name, folderFullName, `${index}`, item.contentType.subType),
          partId: item.partId
        }
      }))
    }
    catch (e) {
      draftLogger.error(`parse attachments error`);
      return Promise.reject("parse attachments error");
    }
    let _inlineImages: Attachment[] = [];
    try {
      _inlineImages = await Promise.all(inlineImages.map(async (item, index) => {
        const name = item.disposition?.params.filename || item.contentType.params.name;
        return {
          name: name,
          type: item.contentType.type,
          subType: item.contentType.subType,
          id: item.contentId,
          encoding: item.encoding,
          size: item.size,
          index: index,
          localPath: await this.downloadStreamIfNeededAndDecode(item as Mime, mid, index, true, name, folderFullName, item.contentId, item.contentType.subType),
          partId: item.partId
        }
      }))
    }
    catch (e) {
      let info = "parse inlineImages error"
      draftLogger.error(info);
      return Promise.reject(info);
    }
    const envelope = mimeMail.getEnvelope();
    try {
      //解析获取的数据
      let from = envelope.from;
      let to = envelope.to.filter((item) => {
        return (item.name.length > 0 || item.email.length > 0)
      });
      envelope.to = to;
      let cc = envelope.cc.filter((item) => {
        return (item.name.length > 0 || item.email.length > 0)
      });
      envelope.cc = cc;
      let bcc = envelope.bcc.filter((item) => {
        return (item.name.length > 0 || item.email.length > 0)
      });
      envelope.bcc = bcc;
      //属性结构
      mail.sender = this.mailAddressToString(from);
      mail.receiver = this.mailAddressListToString(to);
      mail.cc = this.mailAddressListToString(cc);
      mail.bcc = this.mailAddressListToString(bcc);
      logger.trace(`[__draft] [parseEml] [parse result: ${JSON.stringify(cc)}] [${JSON.stringify(bcc)}]`);
    }
    catch (e) {
      let info = "parse cc or sender error"
      draftLogger.error(info);
      return Promise.reject(info);
    }
    try {
      let subject = envelope.subject;
      let date = envelope.date;

      let htmlPart: Mime | undefined = mimeMail.getHtmlPart() as Mime | undefined;
      if (htmlPart) {
        let htmlPath = this.getHtmlPathForMail(mid, this.account, folderFullName);
        await ParseUtil.decodeMimePartToBuffer(htmlPart, htmlPath);
        mail.digest = "";
        let htmlPartId = mimeMail.getHtmlPartId();
        mail.setHtml(htmlPath, htmlPartId);
      }

      let textPart: Mime | undefined = mimeMail.getPlainPart() as Mime | undefined;
      if (textPart) {
        let textPath = this.getTextPathForMail(mid, this.account, folderFullName);
        await ParseUtil.decodeMimePartToBuffer(textPart, textPath);
        let textPartId = mimeMail.getPlainPartId();
        mail.setText(textPath, textPartId);
      }

      mail.subject = subject;
      mail.receivedDate = date.getTime();
      mail.size = size;
      mail.setEnvelope(envelope);
    }
    catch (e) {
      let info = "parse subject or html error"
      draftLogger.error(info);
      return Promise.reject(info);
    }
    const st = mimeMail.getStructure()
    try {
      logger.trace("set mail structure", st)
      mail.setMailStructure(st);
      mail.setAttachments(_attachments);
      mail.setInlineFiles(_inlineImages)
    }
    catch (e) {
      let info = "parse mail structre error"
      draftLogger.error(info);
      return Promise.reject(info);
    }
  }

  /**
   * 更新邮件状态,并更新对应文件夹的未读数
   * @param mailId
   * @param folderFullName
   * @param attributes
   * @param modifyType
   * @returns
   */
  async setMailAttributes(mailId: string, folderFullName: string, attributes: MailAttribute[], modifyType: '' | '+' | '-'): Promise<MailAttribute[]> {
    let folder = await this.storageDB.folderDaoImpl.getFolderByFullName(folderFullName);
    if (!folder) {
      countLogger.warn(`no such folder: ${folderFullName}`)
      return Promise.reject("no such folder");
    }
    // let attr = await this.storageDB.mailDaoImpl.setMailAttributes(mailId, folder.id, attributes, modifyType);
    let mail = await this.storageDB.mailDaoImpl.getMail(mailId,folder.id);
    let originIsNew = mail.isNew;
    mail.updateAttr(attributes,modifyType);
    let newAttr = mail.getAttr();
    await this.storageDB.mailDaoImpl.updateMail(mail);

    //updateBySet
    let isUpdateBySet:boolean = false;
    if(isUpdateBySet) {
      let newIsNew = mail.isNew;
      if (!originIsNew && newIsNew) {
        folder.unReadCount++;
      }
      else {
        folder.unReadCount--;
      }
      await this.storageDB.folderDaoImpl.updateFolder(folder);
    }
    else {
      //updateByCal
      await this.updateFolderUnreadCountByCal(folder);
    }

    countLogger.trace(`[MailStorage] [setMailAttr] [end] [attr is: ${JSON.stringify(newAttr)},folder count is: ${folder.unReadCount}]`)
    //todo:增加对批量操作的支持,每个文件夹只更新一次.

    return newAttr;
  }

  mailAddressToString(emailAddress: EmailAddress): string {
    return emailAddress.name + emailAddress.email
  }

  mailAddressListToString(emailAddress: EmailAddress[]): string {
    return emailAddress.map((item: EmailAddress) => {
      return this.mailAddressToString(item)
    }).join("");
  }


  /**
   * 如果文件不存在就下载到对应文件.
   * @param stream
   * @param uri
   * @param index
   * @param isInline
   * @param fileName
   * @returns
   */
  private async downloadStreamIfNeeded(stream: IBuffer, mid: string, index: number, isInline: boolean, rawFileName: string, folderFullName: string,cid:string,fileType:string): Promise<string | null> {
    let relativePath = EmlFileManager.getRelativeDirPathForMailAttachment(this.account, mid, folderFullName, isInline);
    let fileName = EmlFileManager.getAttachNameWithRawName(cid,rawFileName, fileType)
    let filePath = `${this.fileRootDir}/${relativePath}/${fileName}`
    let b = FileManager.isFileExisted(filePath);
    if (!b) {
      let content = await stream.readAll();
      try {
        FileManager.writeToFile(content, this.fileRootDir, relativePath, fileName);
      } catch (e) {
        logger.warn(e)
        return null
      }
    }
    return filePath;
  }
  private async downloadStreamIfNeededAndDecode(part:Mime, mid: string, index: number, isInline: boolean, rawFileName: string, folderFullName: string,cid:string,fileType:string): Promise<string | null> {
    let relativePath = EmlFileManager.getRelativeDirPathForMailAttachment(this.account, mid, folderFullName, isInline);
    let fileName = EmlFileManager.getAttachNameWithRawName(cid,rawFileName, fileType)
    let filePath = `${this.fileRootDir}/${relativePath}/${fileName}`
    let b = FileManager.isFileExisted(filePath);
    if (!b) {
      ParseUtil.decodeMimePartToBuffer(part,filePath);
    }
    return filePath;
  }

  /**
   * 处理多次调用setFolderList,会插入
   * @param folderList
   * @returns
   */
  async setFolderList(folderList: FolderData[]): Promise<void> {
    await this.init();
    if(this.p_setFolderList) {
      this.p_setFolderList = this.p_setFolderList.then(()=> {
        return this.setFolderListOnce(folderList);
      })
    }
    else {
      this.p_setFolderList = this.setFolderListOnce(folderList);
    }
    this.p_setFolderList.then(()=>{
      folderLogger.trace(`[setFolderList] [clear p_setFolderList]`)
      this.p_setFolderList = null;
    })
  }
  /*
   * 同步文件夹数据
   * 存储所有的文件夹.
   * 一次同步过程中,应该只调用一次.
   * 1,先全部存进去
   * 2,然后更新id
   * 3,本地中多余的文件夹,需要删除.
   * 4,文件夹的未读数,不用存储服务端中的数据,根据数据库的mail实际情况查一遍.
   * @param folderList
   * @returns
   */
  async setFolderListOnce(folderList: FolderData[]): Promise<void> {
    await this.init();
    folderLogger.trace(`[MailStorage/setFolderList] [${JSON.stringify(folderList)}]`)
    let localFolders = await this.getFolderList();
    for (const folder of folderList) {
      //重要文件夹跳过不创建.
      if (folder.fullName == IMPORTANT_FOLDER_NAME) continue;
      let entity = EntityTransformer.getFolderEntity(folder, null);
      await this.storageDB.folderDaoImpl.insertOrUpdateFolderWithFullName(folder.fullName, entity);
      //删除掉文件夹一样的,最终保留的是只有本地有,远程没有的数据.
      localFolders = localFolders.filter((item) => {
        return item.fullName != folder.fullName
      });
    }
    if (localFolders.length > 0) {
      logger.debug(`[setFolderList][__folder] [delete local only folder:] [${JSON.stringify(localFolders)}]`);
      for (const localFolder of localFolders) {
        let localFolderEntity = await this.storageDB.folderDaoImpl.getFolderByFullName(localFolder.fullName);
        await this.storageDB.folderDaoImpl.deleteFolder(localFolderEntity);
        await this.storageDB.mailDaoImpl.deleteMailByRawSql(null, localFolderEntity.id);
      }
    }
  }

  /**
   * 创建后需要init操作
   * 1,初始化数据库和表.
   * 2,设置propertis属性.
   */
  async init():Promise<void> {
    if (!this.isInit) {
      await this.storageDB.createDb();
      this.isInit = true;
    }
  }

  /**
   * 删除账号时调用:
   * 1,清除数据库资源
   * 2,以及文件缓存.
   * @returns
   */
  async release():Promise<void> {
    await this.storageDB.deleteDb();
    let userDirRelativePath = EmlFileManager.getRelativeDirPathForAccount(this.account);
    let userDir = `${this.fileRootDir}/${userDirRelativePath}`
    this.removeDirR(userDir);
  }
}
